#include "m_curl.h"
#include "segment.h"
#include "span.h"
#include "common.h"
#include "agent.h"
#include "helper.h"
#include "utils.h"
#include "scaler.h"
#include "pushgateway_client.h"
// #include "skywalking_client.h"

#include <map>
#include <vector>
#include <string>
#include <curl/curl.h>
#include <unistd.h>

#include "ext/standard/url.h"

// int is_record = 0;
std::map<long, std::map<std::string, Scaler *>> msc;

void init_scaler_map(long addr)
{
    if (msc.count(addr) <= 0)
    {
        Scaler *total = new Scaler;
        Scaler *second = new Scaler;
        msc[addr]["total"] = total;
        msc[addr]["second"] = second;
    }
}

void monitorpro_curl_setopt_handler(zend_execute_data *execute_data, long start_micro_time, long end_micro_time, std::string class_name, std::string function_name)
{
    zval *ch, *opt, *value;
    std::string method = "GET";
    std::string item_name = ITEM_HTTP_METHOD;

    ch = func_get_arg(execute_data, 1);
    opt = func_get_arg(execute_data, 2);
    value = func_get_arg(execute_data, 3);

    if (ch == nullptr)
    {
        return;
    }

    long opt_value = opt == nullptr ? 0 : Z_LVAL_P(opt);
    if (MONITORPRO_CURLOPT_POST == opt_value)
    {
        switch (Z_TYPE_P(value))
        {
        case IS_BOOL:
            method = Z_BVAL_P(value) ? VALUE_HTTP_METHOD_POST : VALUE_HTTP_METHOD_GET;
            break;
        case IS_STRING:
            method = Z_LVAL_P(value);
        case IS_LONG:
            method = Z_LVAL_P(value) ? VALUE_HTTP_METHOD_POST : VALUE_HTTP_METHOD_GET;
            break;
        default:
            method = VALUE_HTTP_METHOD_GET;
            break;
        }
    }

    // long addr = reinterpret_cast<long>(ch);
    long addr = 0;
    init_scaler_map(addr);
    msc[addr]["total"]->addItem(item_name, method);
    msc[addr]["second"]->addItem(item_name, method);

    // if (CURLOPT_HTTPHEADER == opt_value && Z_TYPE_P(value) == IS_ARRAY)
    // {
    //     add_index_zval(&MONITORPRO_G(curl_header), Z_RESVAL_P(ch), value);
    // }
}

void monitorpro_curl_setopt_array_handler(zend_execute_data *execute_data, long start_micro_time, long end_micro_time, std::string class_name, std::string function_name)
{
    zval *ch, *arr;
    std::string method = "GET";
    std::string item_name = ITEM_HTTP_METHOD;

    ch = func_get_arg(execute_data, 1);
    arr = func_get_arg(execute_data, 2);

    if (ch == nullptr)
    {
        return;
    }

    zval **value;
    if (SUCCESS == zend_hash_index_find(HASH_OF(arr), MONITORPRO_CURLOPT_POST, (void **)&value))
    {
        switch (Z_TYPE_PP(value))
        {
        case IS_BOOL:
            method = Z_BVAL_PP(value) ? VALUE_HTTP_METHOD_POST : VALUE_HTTP_METHOD_GET;
            break;
        case IS_STRING:
            method = Z_LVAL_PP(value);
        case IS_LONG:
            method = Z_LVAL_PP(value) ? VALUE_HTTP_METHOD_POST : VALUE_HTTP_METHOD_GET;
            break;
        default:
            method = VALUE_HTTP_METHOD_GET;
            break;
        }
    }

    // long addr = reinterpret_cast<long>(ch);
    long addr = 0;
    init_scaler_map(addr);
    msc[addr]["total"]->addItem(item_name, method);
    msc[addr]["second"]->addItem(item_name, method);

    // zval **http_header;
    // if (SUCCESS == zend_hash_index_find(HASH_OF(arr), CURLOPT_HTTPHEADER, (void **)&http_header))
    // {
    //     add_index_zval(&MONITORPRO_G(curl_header), Z_RESVAL_P(ch), *http_header);
    // }
}

void monitorpro_pre_curl_exec_handler(zend_execute_data *execute_data, std::string class_name, std::string function_name)
{
    zval *ch = func_get_arg(execute_data, 1);
    if (ch == nullptr)
    {
        return;
    }

    // 执行系统 curl_getinfo 函数
    zval func;
    zval *args[1];
    zval curl_info;
    args[0] = ch;
    ZVAL_STRING(&func, "curl_getinfo", 0);
    call_user_function(EG(function_table), NULL, &func, &curl_info, 1, args TSRMLS_CC);

    zval **z_url;
    char *url = nullptr;
    php_url *url_parse = nullptr;
    if (SUCCESS == zend_hash_find(HASH_OF(&curl_info), "url", sizeof("url"), (void **)&z_url))
    {
        url = Z_STRVAL_PP(z_url);
        if (strlen(url) > 0 && (starts_with("http://", url) || starts_with("https://", url)))
        {
            url_parse = php_url_parse(url);
            if (url_parse->scheme != nullptr && url_parse->host != nullptr)
            {
                // is_record = 1;
            }
        }
    }

    // set header
    // int is_emalloc = 0;
    zval **option;
    // if (is_record)
    // {
    if (SUCCESS != zend_hash_index_find(HASH_OF(&MONITORPRO_G(curl_header)), Z_RESVAL_P(ch), (void **)&option))
    {
        *option = (zval *)emalloc(sizeof(zval));
        bzero(*option, sizeof(zval));
        array_init(*option);
        // is_emalloc = 1;
    }

    // for php7.3.0+
#if PHP_VERSION_ID >= 70300
    char *php_url_scheme = ZSTR_VAL(url_parse->scheme);
    char *php_url_host = ZSTR_VAL(url_parse->host);
    char *php_url_path = ZSTR_VAL(url_parse->path);
    char *php_url_query = ZSTR_VAL(url_parse->query);
#else
    char *php_url_scheme = url_parse->scheme;
    char *php_url_host = url_parse->host;
    char *php_url_path = url_parse->path;
    char *php_url_query = url_parse->query;
#endif
    std::string url_path = php_url_path == nullptr ? "/" : std::string(php_url_path);
    std::string url_query = php_url_query == nullptr ? "" : std::string(php_url_query);
    std::string url_host = php_url_host == nullptr ? "" : std::string(php_url_host);

    int peer_port;
    if (url_parse->port)
    {
        peer_port = url_parse->port;
    }
    else
    {
        if (strcasecmp("http", php_url_scheme) == 0)
        {
            peer_port = 80;
        }
        else
        {
            peer_port = 443;
        }
    }

    // SkywalkingClient *skywalking_client = SkywalkingClient::newInstance();
    // Segment *segment = skywalking_client->getSegment();
    // Span *span = segment->createSpan(SkySpanType::Exit, SkySpanLayer::Http, 8002);
    // span->setPeer(url_host + ":" + std::to_string(peer_port));
    // span->setOperationName(url_path);
    // span->addTag("url", url);

    // long addr = reinterpret_cast<long>(ch);
    long addr = 0;
    init_scaler_map(addr);
    msc[addr]["second"]->addItem(ITEM_HTTP_FULL_PATH, "");
    msc[addr]["second"]->addItem(ITEM_HTTP_PATH, url_path);
    msc[addr]["second"]->addItem(ITEM_HTTP_PEER, url_host + ":" + std::to_string(peer_port));
    msc[addr]["second"]->setIndex(INDEX_HTTP_CLIENT_HANDLING_SECCONDS);
    msc[addr]["total"]->addItem(ITEM_HTTP_FULL_PATH, "");
    msc[addr]["total"]->addItem(ITEM_HTTP_PATH, url_path);
    msc[addr]["total"]->addItem(ITEM_HTTP_PEER, url_host + ":" + std::to_string(peer_port));
    msc[addr]["total"]->setIndex(INDEX_HTTP_CLIENT_HANDLED_TOTAL);

    // std::string sw_header = segment->createHeader(span);
    // std::string sw_header_full = "sw8: " + sw_header;
    // add_next_index_string(*option, sw_header_full.c_str(), sw_header_full.length() + 1);

    // zval *argv[3];
    // zval ret;
    // zval *opt_val;
    // ALLOC_INIT_ZVAL(opt_val);
    // ZVAL_LONG(opt_val, CURLOPT_HTTPHEADER);
    // argv[0] = ch;
    // argv[1] = opt_val;
    // argv[2] = *option;
    // ZVAL_STRING(&func, "curl_setopt", 0);
    // call_user_function(EG(function_table), NULL, &func, &ret, 3, argv TSRMLS_CC);

    // zval_dtor(&func);
    // zval_dtor(&ret);
    // zval_dtor(opt_val);
    // zval_dtor(argv[0]);
    // zval_dtor(argv[1]);
    // zval_dtor(argv[2]);
    // if (is_emalloc)
    // {
    //     zval_ptr_dtor(option);
    // }
    // }

    // zval_dtor(&curl_info);
    if (url_parse != nullptr)
    {
        php_url_free(url_parse);
    }
}

void monitorpro_post_curl_exec_handler(zend_execute_data *execute_data, long start_micro_time, long end_micro_time, std::string class_name, std::string function_name)
{
    zval *ch = func_get_arg(execute_data, 1);
    if (ch == nullptr)
    {
        return;
    }

    // if (is_record == 1)
    // {
    // SkywalkingClient *skywalking_client = SkywalkingClient::newInstance();
    // Segment *segment = skywalking_client->getSegment();
    // Span *span = segment->getLastSpan();

    zval func;
    zval *args[1];
    zval url_response;
    args[0] = ch;
    ZVAL_STRING(&func, "curl_getinfo", 0);
    call_user_function(EG(function_table), NULL, &func, &url_response, 1, args TSRMLS_CC);
    // zval_dtor(&func);
    // zval_dtor(args[0]);

    std::string url_path = "/";
    std::string url_query = "";
    std::string url_host = "";
    int peer_port = 80;

    zval **z_url;
    char *url = nullptr;
    php_url *url_parse = nullptr;
    if (SUCCESS == zend_hash_find(HASH_OF(&url_response), "url", sizeof("url"), (void **)&z_url))
    {
        url = Z_STRVAL_PP(z_url);
        if (strlen(url) > 0 && (starts_with("http://", url) || starts_with("https://", url)))
        {
            url_parse = php_url_parse(url);
            if (url_parse->scheme != nullptr && url_parse->host != nullptr)
            {
                // for php7.3.0+
#if PHP_VERSION_ID >= 70300
                char *php_url_scheme = ZSTR_VAL(url_parse->scheme);
                char *php_url_host = ZSTR_VAL(url_parse->host);
                char *php_url_path = ZSTR_VAL(url_parse->path);
                char *php_url_query = ZSTR_VAL(url_parse->query);
#else
                char *php_url_scheme = url_parse->scheme;
                char *php_url_host = url_parse->host;
                char *php_url_path = url_parse->path;
                char *php_url_query = url_parse->query;
#endif
                url_path = php_url_path == nullptr ? "/" : std::string(php_url_path);
                url_query = php_url_query == nullptr ? "" : std::string(php_url_query);
                url_host = php_url_host == nullptr ? "" : std::string(php_url_host);

                if (url_parse->port)
                {
                    peer_port = url_parse->port;
                }
                else
                {
                    if (strcasecmp("http", php_url_scheme) == 0)
                    {
                        peer_port = 80;
                    }
                    else
                    {
                        peer_port = 443;
                    }
                }
            }
        }
    }

    // zval **z_http_code;
    zval **z_total_time, **z_primary_ip, **z_primary_port;
    std::string http_code = "unknown", total_time = "unknown", primary_ip = "unknown", primary_port = "unknown";
    // if (SUCCESS == zend_hash_find(HASH_OF(&url_response), "http_code", sizeof("http_code"), (void **)&z_http_code))
    // {
    //     span->addTag("status_code", std::to_string(Z_LVAL_PP(z_http_code)));
    //     if (Z_LVAL_PP(z_http_code) >= 400)
    //     {
    //         span->setIsError(true);
    //     }
    //     else
    //     {
    //         span->setIsError(false);
    //     }
    //     http_code = std::to_string(Z_LVAL_PP(z_http_code));
    // }

    if (SUCCESS == zend_hash_find(HASH_OF(&url_response), "total_time", sizeof("total_time"), (void **)&z_total_time))
    {
        total_time = std::to_string(Z_DVAL_PP(z_total_time));
    }
    if (SUCCESS == zend_hash_find(HASH_OF(&url_response), "primary_ip", sizeof("primary_ip"), (void **)&z_primary_ip))
    {
        primary_ip = Z_STRVAL_PP(z_primary_ip);
    }
    if (SUCCESS == zend_hash_find(HASH_OF(&url_response), "primary_port", sizeof("primary_port"), (void **)&z_primary_port))
    {
        primary_port = std::to_string(Z_LVAL_PP(z_primary_port));
    }

    // zval_dtor(&url_response);

    // span->setStartTime(start_micro_time / 1000);
    // span->setEndTime(end_micro_time / 1000);

    // 拼装 pushgateway 数据
    // long addr = reinterpret_cast<long>(ch);
    long addr = 0;
    init_scaler_map(addr);
    // msc[addr]["second"]->addItem(ITEM_HTTP_FULL_PATH, url_path + "?" + url_query);
    msc[addr]["second"]->addItem(ITEM_HTTP_PATH, url_path);
    msc[addr]["second"]->addItem(ITEM_HTTP_PEER, url_host + ":" + std::to_string(peer_port));
    msc[addr]["second"]->setIndex(INDEX_HTTP_CLIENT_HANDLING_SECCONDS);
    // msc[addr]["total"]->addItem(ITEM_HTTP_FULL_PATH, url_path + "?" + url_query);
    msc[addr]["total"]->addItem(ITEM_HTTP_PATH, url_path);
    msc[addr]["total"]->addItem(ITEM_HTTP_PEER, url_host + ":" + std::to_string(peer_port));
    msc[addr]["total"]->setIndex(INDEX_HTTP_CLIENT_HANDLED_TOTAL);

    msc[addr]["second"]->addItem(ITEM_HTTP_ADDRESS, primary_ip + ":" + primary_port);
    msc[addr]["total"]->addItem(ITEM_HTTP_CODE, http_code);
    msc[addr]["total"]->addItem(ITEM_HTTP_ADDRESS, primary_ip + ":" + primary_port);

    PushGatewayClient *pushgateway_client = PushGatewayClient::newInstance();
    pushgateway_client->setBucket(msc[addr]["second"], Z_DVAL_PP(z_total_time));
    pushgateway_client->setCounter(msc[addr]["total"]);

    delete msc[addr]["total"];
    delete msc[addr]["second"];
    msc[addr]["total"] = nullptr;
    msc[addr]["second"] = nullptr;
    msc.erase(addr);

    // zval_dtor(&url_response);
    if (url_parse != nullptr)
    {
        php_url_free(url_parse);
    }
    // }
}

void monitorpro_curl_close_handler(zend_execute_data *execute_data, long start_micro_time, long end_micro_time, std::string class_name, std::string function_name)
{
    // 获取函数传参
    zval *ch = func_get_arg(execute_data, 1);
    if (ch != nullptr)
    {
        // long addr = reinterpret_cast<long>(ch);
        long addr = 0;
        delete msc[addr]["total"];
        delete msc[addr]["second"];
        msc[addr]["total"] = nullptr;
        msc[addr]["second"] = nullptr;
        msc.erase(addr);
    }

    zval **http_header;
    if (SUCCESS == zend_hash_index_find(HASH_OF(&MONITORPRO_G(curl_header)), Z_RESVAL_P(ch), (void **)&http_header))
    {
        zend_hash_index_del(Z_ARRVAL_P(&MONITORPRO_G(curl_header)), Z_RESVAL_P(ch));
    }
}
